home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992 June: ROMin Holiday / ADC Developer CD (1992-06) (''ROMin Holiday'')_iso / Developer Connection - 06-1992.iso / Developer Essentials / DTS Sample Code / Macintosh Sample Code / SC.024.SoundApp / SoundUnit.p < prev   
Encoding:
Text File  |  1991-10-09  |  54.7 KB  |  1,531 lines  |  [TEXT/MPS ]

  1. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. #
  3. # Apple Macintosh Developer Technical Support
  4. #
  5. # SoundUnit
  6. #
  7. # SoundUnit.p    - MPW 3.0/3.1 Pascal Source
  8. #
  9. # Versions:
  10. #             1.03                    May, 1990
  11. #            1.1b1                    Nov, 1990            MPW 3.2 update
  12. #
  13. # Components:
  14. #             SoundApp.make        May 1, 1990            MPW build script
  15. #             SoundApp.p            May 1, 1990            Pascal source code
  16. #             SoundApp.r            May 1, 1990            Rez source code
  17. #             SoundAppSnds.r        May 1, 1990            Rez source code
  18. #             SoundUnit.p            May 1, 1990            Pascal source code
  19. #
  20. # Version comments
  21. #
  22. #    1.1:  This is the "new" SoundUnit which adds some new features.
  23. #            • Some knowledge of the new Sound Manager is present
  24. #              in areas that were work-arounds for old Sound Manager bugs
  25. #            • Conversion to MPW 3.2 was established (with some amount of pain)
  26. #            • Added the new Sound Manager error strings
  27. #            • Checking of the sound header for supported encode values
  28. #            • The amp value is ignored (never used) in a freqDurationCmd
  29. #            • Added functions to test sound hardware/software features
  30. #              such as stereo, MACE, Sound Input
  31. #
  32. #
  33. # Formatting was done with FONT = Monaco, SIZE = 9, TABS = 3
  34. #
  35. #
  36. # SoundApp.p is a sample application source file for demonstrating the
  37. # Sound Manager.  This portion of the source code handles the Sound Manager
  38. # part of the application.  This UNIT can be used by others.
  39. #
  40. # Jim Reekes E.O., Macintosh Developer Technical Support
  41. # Tuesday, January 30, 1990  1:01 PM
  42. #
  43. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  44.  
  45. UNIT SoundUnit;
  46.  
  47. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  48. INTERFACE
  49. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  50.  
  51. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  52. USES
  53. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  54. {Only include the interface files that I really need.  This helps MPW
  55.  to compile faster.}
  56.  
  57.     Types, Traps, Memory, Resources, OSUtils, Errors, FixMath, SANE, Sound,
  58.     SoundInput, GestaltEqu;
  59.  
  60. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  61. CONST
  62. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  63.  
  64. {refer to the SoundAppSnds.r file for documentation on these values}
  65.     kOctave1 =            0;                    {octaves of MIDI values}
  66.     kOctave2 =            12;
  67.     kOctave3 =            24;
  68.     kOctave4 =            36;
  69.     kOctave5 =            48;
  70.     kOctave6 =            60;
  71.     kOctave7 =            72;
  72.     kOctave8 =            84;
  73.     kOctave9 =            96;
  74.     kOctave10 =            108;
  75.     kOctave11 =            120;
  76.  
  77.     Akey =                -3;                {the key A}
  78.     Bbkey =                -2;                {the key B flat}
  79.     Bkey =                -1;                {the key B}
  80.     Ckey =                0;                    {the key C}
  81.     Dbkey =                1;                    {the key D flat}
  82.     Dkey =                2;                    {the key D}
  83.     Ebkey =                3;                    {the key D flat}
  84.     Ekey =                4;                    {the key E}
  85.     Fkey =                5;                    {the key F}
  86.     Gbkey =                6;                    {the key G flat}
  87.     Gkey =                7;                    {the key G}
  88.     Abkey =                8;                    {the key A flat}
  89.  
  90. {These are other constants used in the SoundUnit}
  91.     kInitNone =            0;                    {no init options}
  92.     kWait =                FALSE;            {wait for the channel}
  93.     kSMAsynch =            TRUE;                {asynchronous Sound Manager call}
  94.     kMiddleC =            kOctave6 + Ckey; {MIDI value of middle C}
  95.     kWaveSize =            512;                {standard size of wave table}
  96.     kSyncID =            $12345678;        {identifier used in syncCmd}
  97.     kOneSecond =        2000;                {one second frequency duration}
  98.  
  99. {These are used as flags in the sound channel to determine the state
  100.  of that channel.  The 'snth' IDs are used when the channel is in use
  101.  to determine what that channel in intended for.}
  102.     kNoSynth =            0;                    {no synth is specified}
  103.     kChanComplete =    -1;                {channel has completed}
  104.     kChanFree =            MAXLONGINT;        {channel is not in use}
  105.     kSoundComplete =    $1234;            {flag for callBackCmd}
  106.  
  107. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  108. TYPE
  109. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  110.  
  111.     SndCmdPtr =            ^SndCommand;    {Ptr to a sound command, for type coersion}
  112.     IntPtr =                ^INTEGER;        {Ptr to a INTEGER, for type coersion}
  113.  
  114. {I have declared a few TYPEs below to help examine 'snd ' resources.
  115.  I have to break them up into individual pieces because they are
  116.  variable sized records.}
  117.  
  118.     Snd1Header =        RECORD
  119.         format:            INTEGER;
  120.         numSynths:        INTEGER;
  121.     END;
  122.     Snd1HdrPtr =        ^Snd1Header;
  123.     Snd1HdrHndl =        ^Snd1HdrPtr;
  124.  
  125.     SynthInfo =            RECORD
  126.         synthID:            INTEGER;
  127.         initOption:        LONGINT;
  128.     END;
  129.     SynthInfoPtr =        ^SynthInfo;
  130.  
  131.     Snd2Header =        RECORD
  132.         format:            INTEGER;
  133.         refCount:        INTEGER;
  134.     END;
  135.     Snd2HdrPtr =        ^Snd2Header;
  136.     Snd2HdrHndl =        ^Snd2HdrPtr;
  137.  
  138. {I have created my own sound channel type.  It extends the normal
  139.  sound channel by adding a few fields.  To confirm that the channel
  140.  in question is mine, I set the userInfo field to something that I
  141.  can recognize.  I keep the 'snd ' resource handle associated to this
  142.  channel too.  This allows me to dispose of the data once the channel
  143.  has completed its duties.}
  144.  
  145.     MyChanType =        RECORD                {this is a 1064 byte structure}
  146.         theChan:            SndChannel;            {must keep sound channel as first field}
  147.         dataHandle:        Handle;
  148.     END;
  149.     MyChanPtr =            ^MyChanType;
  150.  
  151.  
  152. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  153. VAR {The “g” prefix is used to emphasize that a variable is global.}
  154. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  155.  
  156. {These four global pointers are to my sound channels.  One of them has to
  157.  be global and I could utilize the fact that they are a linked list.  That
  158.  would complicate things to some degree, and it really wouldn’t have that
  159.  much of an advantage.}
  160.  
  161.     gChan1:             MyChanPtr;
  162.     gChan2:             MyChanPtr;
  163.     gChan3:             MyChanPtr;
  164.     gChan4:             MyChanPtr;
  165.  
  166. {This is the global flag that is set once the sound channel’s call back
  167.  procedure has been called.  This, in my case, means the channel has
  168.  completed its duties and is time for disposing.}
  169.  
  170.     gCalledBack:    BOOLEAN;
  171.  
  172. {gChanOpen is a flag set to determine if the application has a sound
  173.  channel open.  It’s really not feasible to determine if a sound is being
  174.  made at any given point.}
  175.  
  176.      gChanOpen:        BOOLEAN;
  177.  
  178. {gNewSndMgr is a flag used to determine if the application is running
  179.  with the new Sound Manager.  This was initially shipped in System 6.0.6.
  180.  This flag is setup in the InitSoundUnit routine and used by the rest of this
  181.  UNIT.  This is new for version 1.1}
  182.  
  183.     gNewSndMgr:        BOOLEAN;
  184.  
  185. {gHasMACE is a flag used to determine if the MACE compression/decompression
  186.  routines are available.  It is necessary for MACE to be present before using
  187.  a compressed sound.  This is new for version 1.1}
  188.  
  189.     gHasMACE:        BOOLEAN;
  190.  
  191. {gHasSndInput is a flag used to determine if the Sound Input Manager is
  192.  available.  This is new for version 1.1}
  193.  
  194.     gHasSndInput:    BOOLEAN;
  195.  
  196. {gHasStereo is a flag used to determine if stereo hardware is present.
  197.  This is new for version 1.1}
  198.  
  199.     gHasStereo:    BOOLEAN;
  200.  
  201.  
  202.  
  203. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  204.  
  205. {The routines below are for public consumption in this UNIT.  Note that
  206.  these all return standard sound channels to allow easy of modifications.
  207.  I can change the structure of my own sound channels and the code without
  208.  forcing a change in the application that uses this UNIT.
  209.  
  210.  VERSION 1.1: No longer putting the SANELib into a seperate unit, which then
  211.  needs to be unloaded.  Instead, I merge it into the Main segment.  Refer to
  212.  the Make file for more information.}
  213.  
  214. PROCEDURE _SoundUnit;
  215.  
  216. FUNCTION InitSoundUnit: OSErr;
  217. FUNCTION HasNewSndMgr: BOOLEAN;
  218. FUNCTION HasMACE: BOOLEAN;
  219. FUNCTION HasSoundInput: BOOLEAN;
  220. FUNCTION HasStereo: BOOLEAN;
  221. FUNCTION SoundCompletion: BOOLEAN;
  222. FUNCTION SndChanOpen: BOOLEAN;
  223. FUNCTION SetSquareTimbre(squareChan: SndChannelPtr; timbre: INTEGER;
  224.                                 immediate: BOOLEAN): OSErr;
  225. FUNCTION SendFreqDur(chan: SndChannelPtr; duration: INTEGER; freq: LONGINT): OSErr;
  226. FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
  227. FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
  228. PROCEDURE DoSoundComplete;
  229. PROCEDURE FreeAllChans;
  230. PROCEDURE FreeSoundUnit;
  231. FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
  232. FUNCTION HoldSnd(sndHandle: Handle): OSErr;
  233. FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
  234. FUNCTION GetSndDataOffset(sndHandle: Handle;
  235.                                     VAR dataType, waveLength: INTEGER): LONGINT;
  236. FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
  237.                                                         sndInstrument: Handle): OSErr;
  238. FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
  239.                             waveLength: INTEGER): OSErr;
  240. FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
  241.                                   waveChan3, waveChan4: SndChannelPtr): OSErr;
  242. FUNCTION GetSquareChan(VAR squareChan: SndChannelPtr; timbre: INTEGER): OSErr;
  243. FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
  244. FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
  245. FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
  246. FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
  247.                             song1, song2, song3, song4: Handle): OSErr;
  248. FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
  249. FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
  250.  
  251.  
  252. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  253. IMPLEMENTATION
  254. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  255. {$S SoundUnit}
  256. PROCEDURE _SoundUnit;
  257.  
  258. {This is a dummy routine to allow the application to unload this UNIT.}
  259.  
  260. BEGIN
  261. END;
  262.  
  263. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  264. {$S Initialization}
  265. FUNCTION InitSoundUnit: OSErr;
  266.  
  267. {Create storage for each of the four channels (4 * 1064 bytes) and
  268.  initialize them.  If any of the channels cannot be allocated, return an
  269.  error.  Also initialized our global flag “gCalledBack.” An interesting
  270.  modification would be to allow the caller to pass in the number of
  271.  channels the application really intends on using. If the user only wants
  272.  one channel, then we could just allocate one instead of four.  These
  273.  channels are used at interrupt time.
  274.  
  275.  Version 1.1: Added the new Sound Manager flag, gNewSndMgr.  First I have to
  276.  test if the _SoundDispatch trap is available, since SndManagerVersion is
  277.  a selector for _SoundDispatch.  This this trap isn't available, then calling
  278.  SndManagerVersion would result in an unimplemented instruction.  If an error 
  279.  is returned, it is the old Sound Manager.  If it is zero, then the call 
  280.  is available (via-MIDI Mgr?) but it isn't the new Sound Manager.  Greater 
  281.  than zero means it is the new Sound Manager.  To determine if MACE is
  282.  available, there are two case.  If the new Sound Manager is running then
  283.  MACE may be built in.  This is easy enough to test for by calling the new
  284.  trap call.  Otherwise I have to test for the presence of the MACE
  285.  snth resources.  Also new is a flag to determine if Sound Input is available.  
  286.  The Gestalt call in System 6.0.7 will only report if the machine has 
  287.  Apple built in Sound Input hardware.  Other third parties may include an 
  288.  external device which will allow recording.  To determine if such a 
  289.  device is present, I use SPBGetIndexdDevice to find the fist one.  If 
  290.  this returns noErr then Sound Input is available. Also, the icon handle 
  291.  returned has to be disposed of.  The MACE snths are only for the old
  292.  Sound Manager which did not have built-in MACE.  They could only be present
  293.  if the user ran the MACE Installer Scripts which are available from APDA.}
  294.  
  295.     
  296.     FUNCTION InitMyChan(VAR newChan: MyChanPtr): BOOLEAN;
  297.     BEGIN
  298.         newChan:= MyChanPtr(NewPtrClear(SizeOf(MyChanType)));
  299.         IF newChan <> NIL THEN BEGIN
  300.             newChan^.theChan.qLength:= stdQLength;
  301.             newChan^.theChan.userInfo:= kChanFree;
  302.             InitMyChan:= TRUE;
  303.         END ELSE
  304.             InitMyChan:= FALSE;
  305.     END;
  306.  
  307. BEGIN
  308.     gChanOpen:= FALSE;
  309.     gCalledBack:= FALSE;
  310.     IF InitMyChan(gChan1) THEN
  311.         IF InitMyChan(gChan2) THEN
  312.             IF InitMyChan(gChan3) THEN
  313.                 IF InitMyChan(gChan4) THEN;
  314.     InitSoundUnit:= MemError;
  315.  
  316.     gNewSndMgr:= HasNewSndMgr;        {is the "new" Sound Manager running?}
  317.     gHasMACE:= HasMACE;                {is MACE (audio compression/expansion) running?}
  318.     gHasSndInput:= HasSoundInput;    {is the Sound Input Manager running?}
  319.     gHasStereo:= HasStereo;            {is there stereo support on this machine?}
  320. END;
  321.  
  322. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  323. {$S Main}
  324. FUNCTION HasNewSndMgr: BOOLEAN;
  325.  
  326. VAR
  327.     response:        LONGINT;
  328.  
  329. {VERSION 1.1:  This is the external routine for users to determine if
  330.  the new Sound Manager is available.}
  331.  
  332. BEGIN
  333.     IF noErr = Gestalt(gestaltSoundAttr, response) THEN
  334.         HasNewSndMgr:= BTst(response, gestaltSoundIOMgrPresent)
  335.     ELSE
  336.         HasNewSndMgr:= FALSE;
  337. END;
  338.  
  339. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  340. {$S Main}
  341. FUNCTION HasMACE: BOOLEAN;
  342.  
  343. VAR
  344.     devName:            Str255;
  345.     environRec:        SysEnvRec;
  346.     theCmd:            SndCommand;
  347.     response:        LONGINT;
  348.  
  349. {VERSION 1.1:  This is the external routine for users to determine if
  350.  audio compression/expansion is available.}
  351.  
  352. BEGIN
  353.     IF HasNewSndMgr THEN
  354.         HasMACE:= MACEVersion.version > 0            {is the built-in MACE here?}
  355.     ELSE BEGIN
  356.         HasMACE:= FALSE    ;                                {default to no MACE}
  357.         theCmd.cmd:= versionCmd;                        {use versionCmd to check}
  358.         theCmd.param1:= 0;
  359.         theCmd.param2:= 0;
  360.         IF noErr = SndControl(MACE3snthID, theCmd) THEN    {if no errors, then...}            
  361.             IF noErr = SndControl(MACE6snthID, theCmd) THEN
  362.                 HasMACE:= TRUE;                            {we got MACE}
  363.     END;
  364. END;
  365.  
  366. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  367. {$S Main}
  368. FUNCTION HasSoundInput: BOOLEAN;
  369.  
  370. VAR
  371.     devName:            Str255;
  372.     devIconHandle:    Handle;
  373.  
  374. {VERSION 1.1:  This is the external routine for users to determine if
  375.  sound input is available.}
  376.  
  377. BEGIN
  378.     HasSoundInput:= FALSE;
  379.     IF HasNewSndMgr THEN BEGIN
  380.         IF SPBVersion.version > 0 THEN BEGIN
  381.             IF SPBGetIndexedDevice(1, devName, devIconHandle) = noErr THEN BEGIN
  382.                 DisposHandle(devIconHandle);
  383.                 HasSoundInput:= TRUE;
  384.             END;
  385.         END;
  386.     END;
  387. END;
  388.  
  389. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  390. {$S Main}
  391. FUNCTION HasStereo: BOOLEAN;
  392.  
  393. VAR
  394.     response:        LONGINT;
  395.  
  396. {VERSION 1.1:  This is the external routine for users to determine if
  397.  sound input is available.}
  398.  
  399. BEGIN
  400.     IF noErr = Gestalt(gestaltSoundAttr, response) THEN
  401.         HasStereo:= BTst(response, gestaltStereoCapability)
  402.     ELSE
  403.         HasStereo:= FALSE;
  404. END;
  405.  
  406. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  407. {$S Main}
  408. FUNCTION SoundCompletion: BOOLEAN;
  409.  
  410. {This routine can be called to determine if the sound has completed.  When
  411.  this is true, the sound data can be disposed of.  The global “gCalledBack”
  412.  determines whether the sound has completed or not and it is set by the
  413.  sound channel’s completion routine.  Soundcompletion is placed in the Main
  414.  segment because it is called by the event loop.}
  415.  
  416. BEGIN
  417.     SoundCompletion:= gCalledBack;
  418. END;
  419.  
  420. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  421. {$S Main}
  422. FUNCTION SndChanOpen: BOOLEAN;
  423.  
  424. {This routine can be called at any time.  It will return TRUE when the
  425.  SoundUnit has an open channel.  This can be can considered the same as
  426.  sound being active.  As long as the channel is open, no other channels can
  427.  be opened.  It is placed in the Main segment because it is called by the
  428.  event loop.}
  429.  
  430. BEGIN
  431.     SndChanOpen:= gChanOpen;
  432. END;
  433.  
  434. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  435. {$S SoundUnit}
  436. FUNCTION SetSquareTimbre(squareChan: SndChannelPtr; timbre: INTEGER;
  437.                                 immediate: BOOLEAN): OSErr;
  438.  
  439. {Given a channel and timbre (sounds like “tom burr”), this will adjust the
  440.  tone quality of the square wave synthesizer.  Changing the tone can only
  441.  be done before playing a frequency.  On a Mac with the Apple Sound Chip, this
  442.  can be done in real time while a sound is playing.  But, since there’s no
  443.  supported method for determining if the ASC is available I have to assume
  444.  that it’s not.  I use the immediate flag to determine if the user wants to
  445.  change the timbre now, or queue the command.  If the queue is full, it
  446.  will wait for the command to be accepted.
  447.  
  448.  BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
  449.  SE where sending a timbreCmd with a timbre of 255 (a legal value) will
  450.  crash.  The difference between 254 and 255 isn’t audible, so I only allow
  451.  a maximum of 254 in any case.}
  452.  
  453. VAR
  454.     theCmd:        SndCommand;
  455.  
  456. BEGIN
  457.     IF timbre > 254 THEN
  458.         timbre:= 254;
  459.     WITH theCmd DO BEGIN
  460.         cmd:= timbreCmd;
  461.         param1:= timbre;
  462.         param2:= 0;
  463.     END;
  464.     IF immediate THEN
  465.         SetSquareTimbre:= SndDoImmediate(squareChan, theCmd)
  466.     ELSE
  467.         SetSquareTimbre:= SndDoCommand(squareChan, theCmd, kWait);
  468. END;
  469.  
  470. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  471. {$S SoundUnit}
  472. FUNCTION SendFreqDur(chan: SndChannelPtr; duration: INTEGER;
  473.                                 freq: LONGINT): OSErr;
  474.  
  475. {Given a channel and frequency/duration information, this will place the
  476.  frequency into the channel’s queue.  The freq is a three-byte parameter
  477.  with the high byte being ignored.  I use SndDoCommand with the noWait
  478.  flag set to wait for the channel to except the command in case the queue
  479.  is currently full.}
  480.  
  481. VAR
  482.     theCmd:        SndCommand;
  483.  
  484. BEGIN
  485.     WITH theCmd DO BEGIN
  486.         cmd:= freqDurationCmd;
  487.         param1:= duration;
  488.         param2:= freq;
  489.     END;
  490.     SendFreqDur:= SndDoCommand(chan, theCmd, kWait);
  491. END;
  492.  
  493. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  494. {$S SoundUnit}
  495. FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
  496.  
  497. {Given a channel, this will place a quietCmd into the channel’s queue.  I
  498.  use SndDoCommand with the noWait flag set to wait for the channel to
  499.  accept the command in the case it is currently full.
  500.  
  501.  BUG NOTE: A sequence of frequencies and rests will not work unless quietCmds
  502.  are between them.  Rests have to be made quiet before they rest, if that
  503.  makes any more sense.  A freqDurationCmd will loop, causing the sound in
  504.  progress to continue, until a quietCmd is received.}
  505.  
  506. VAR
  507.     theCmd:        SndCommand;
  508.  
  509. BEGIN
  510.     WITH theCmd DO BEGIN
  511.         cmd:= quietCmd;
  512.         param1:= 0;
  513.         param2:= 0;
  514.     END;
  515.     IF immediate THEN
  516.         SendQuiet:= SndDoImmediate(chan, theCmd)
  517.     ELSE
  518.         SendQuiet:= SndDoCommand(chan, theCmd, kWait);
  519. END;
  520.  
  521. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  522. {$S SoundUnit}
  523. FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
  524.  
  525. {Given a channel and duration, this will place the rest into the channel’s
  526.  queue.  Before sending a rest a quietCmd is needed.  Rests don’t work
  527.  unless you tell the Sound Manager to be quiet too.  I use SndDoCommand
  528.  with the noWait flag set to wait for the channel to accept the command in
  529.  case it is currently full.}
  530.  
  531. VAR
  532.     theCmd:        SndCommand;
  533.     theErr:        OSErr;
  534.  
  535. BEGIN
  536.     theErr:= SendQuiet(chan, kWait);
  537.     IF theErr = noErr THEN BEGIN
  538.         WITH theCmd DO BEGIN
  539.             cmd:= restCmd;
  540.             param1:= duration;
  541.             param2:= 0;
  542.         END;
  543.         theErr:= SndDoCommand(chan, theCmd, kWait);
  544.     END;
  545.     SendRest:= theErr;
  546. END;
  547.  
  548. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  549. {$S SoundUnit}
  550. PROCEDURE FreeChan(myChan: MyChanPtr);
  551.  
  552. {Test if the channel is free, and if not then call SndDisposeChannel.
  553.  This will release the synthesizer (snth resource) code and the
  554.  required hardware.  If we didn’t do this, no other channels would
  555.  work.  I also test myChan for having a snd resource attached to the
  556.  channel.  If so, then I mark it as purgeable and reset the data to NIL.
  557.  
  558.  BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  559.  a sequence of frequencies would often hang/crash a non-Apple Sound Chip based Mac.
  560.  Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  561.  crashing.}
  562.  
  563. VAR
  564.     theErr:         OSErr;
  565.  
  566. BEGIN
  567.     IF myChan^.theChan.userInfo <> kChanFree THEN BEGIN
  568.         theErr:= SendQuiet(SndChannelPtr(myChan), NOT kWait);        {ignore error}
  569.         theErr:= SndDisposeChannel(SndChannelPtr(myChan), NOT kWait);
  570.         myChan^.theChan.userInfo:= kChanFree;
  571.     END;
  572.     IF myChan^.dataHandle <> NIL THEN BEGIN
  573.         HUnlock(myChan^.dataHandle);
  574.         HPurge(myChan^.dataHandle);
  575.         myChan^.dataHandle:= NIL;
  576.     END;
  577. END;
  578.  
  579. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  580. {$S SoundUnit}
  581. PROCEDURE DoSoundComplete;
  582.  
  583. {This routine is called by an application that established a sound to be
  584.  played asynchronously.  This is, in effect, the routine to be used after
  585.  the completion routine has been called.  The application should call this
  586.  once SoundCompletion returns TRUE.  In the case the application is using
  587.  multiple channels, we will only free a channel once it has been marked as
  588.  kChanComplete.  I do not reset the gCalledBack until all the open channels
  589.  are freed.}
  590.  
  591. BEGIN
  592.     IF gChan1^.theChan.userInfo = kChanComplete THEN
  593.         FreeChan(gChan1);
  594.     IF gChan2^.theChan.userInfo = kChanComplete THEN
  595.         FreeChan(gChan2);
  596.     IF gChan3^.theChan.userInfo = kChanComplete THEN
  597.         FreeChan(gChan3);
  598.     IF gChan4^.theChan.userInfo = kChanComplete THEN
  599.         FreeChan(gChan4);
  600.  
  601.     IF (gChan1^.theChan.userInfo = kChanFree)
  602.      & (gChan2^.theChan.userInfo = kChanFree)
  603.      & (gChan3^.theChan.userInfo = kChanFree)
  604.      & (gChan4^.theChan.userInfo = kChanFree) THEN BEGIN
  605.          gCalledBack:= FALSE;
  606.         gChanOpen:= FALSE;                                {no longer making noises}
  607.     END;
  608. END;
  609.  
  610. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  611. {$S SoundUnit}
  612. PROCEDURE FreeAllChans;
  613.  
  614. {This is the routine that will force all channels to be released.  It also
  615.  resets the gCalledBack flag.  This is used by all routines just before
  616.  opening a new channel to force all channels to be disposed.}
  617.  
  618. BEGIN
  619.     FreeChan(gChan1);
  620.     FreeChan(gChan2);
  621.     FreeChan(gChan3);
  622.     FreeChan(gChan4);
  623.     gCalledBack:= FALSE;
  624.     gChanOpen:= FALSE;                                {no longer making noises}
  625. END;
  626.  
  627. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  628. {$S SoundUnit}
  629. PROCEDURE FreeSoundUnit;
  630.  
  631. {This is the final routine to be called by the application when it is has
  632.  finished using this unit.  This will dispose of all the channels and
  633.  memory used by this unit.}
  634.  
  635. BEGIN
  636.     FreeAllChans;
  637.     IF gChan1 <> NIL THEN
  638.         DisposPtr(Ptr(gChan1));
  639.     IF gChan2 <> NIL THEN
  640.         DisposPtr(Ptr(gChan2));
  641.     IF gChan3 <> NIL THEN
  642.         DisposPtr(Ptr(gChan3));
  643.     IF gChan4 <> NIL THEN
  644.         DisposPtr(Ptr(gChan4));
  645.     gCalledBack:= FALSE;
  646. END;
  647.  
  648. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  649. {$S Main}
  650. PROCEDURE DoCallBack(chan: SndChannelPtr; theCmd: SndCommand);
  651.  
  652. {This will be called at interrupt time by the Sound Manager when it
  653.  receives a callBackCmd.  I use the second parameter of the command to hold
  654.  my application’s A5 reference.  I first set up A5 so that I can access my
  655.  globals.  I mark the given channel as being complete and set gCalledBack
  656.  to TRUE.  This lets the application know that the callBackCmd has been
  657.  processed.  The callBackCmd can be used for other purposes, and the first
  658.  parameter of the command could be a flag to a more extensive routine.
  659.  Synchronizing the application with the channel is possible with this
  660.  method.
  661.  
  662.  WARNING: This routine MUST be resident in memory and cannot make a call
  663.  to a non-resident segment.  I put this into the Main segment because of
  664.  this.
  665.  
  666.  BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  667.  'snd '.  A bogus callBackCmd is placed into the queue immediately after
  668.  the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  669.  my callBackProc to be called when I wasn’t expecting it.  I have been
  670.  using the command’s second parameter to contain my A5 address.  If I’m
  671.  given a bogus callBackCmd, it would be really bad to set A5 address to
  672.  this bogus parameter in the command.  I found that the bogus callBackCmd
  673.  contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  674.  that param1 contains the handle’s state bits (results of HGetState).  To
  675.  work with this bug I set my real callBackCmd’s param1 to a specific value
  676.  when I installed it into the queue.  See the SoundComplete routine.  Then
  677.  I test the callBackCmd to make sure I’m dealing with the real one.}
  678.  
  679. VAR
  680.     theA5:         LONGINT;
  681.  
  682. BEGIN
  683.     IF thecmd.param1 = kSoundComplete THEN BEGIN        {if it’s my callBackCmd}
  684.         theA5:= SetA5(theCmd.param2);                     {refer to tech note 208}
  685.         chan^.userInfo:= kChanComplete;                    {this channel is done}
  686.         gCalledBack:= TRUE;
  687.         theA5:= SetA5(theA5);                                {restore original A5}
  688.     END;
  689. END;
  690.  
  691. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  692. {$S SoundUnit}
  693. FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
  694.  
  695. {I use this to install the callBackCmd into the given channel.  I need to
  696.  pass in our A5 along with the command so that the callBack routine can
  697.  access my globals.  I also wait until the channel is ready for another
  698.  command in the case of the channel being full.  Once the Sound Manager
  699.  calls my call back procedure I will dispose of the channel.  So, this is
  700.  the last sound command to be sent to a channel.  I pass to the call back
  701.  A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.}
  702.  
  703. VAR
  704.     theCmd:        SndCommand;
  705.  
  706. BEGIN
  707.     WITH theCmd DO BEGIN
  708.         cmd:= callBackCmd;
  709.         param1:= kSoundComplete;
  710.         param2:= SetCurrentA5;
  711.     END;
  712.     SoundComplete:= SndDoCommand(chan, theCmd, kWait);
  713. END;
  714.  
  715. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  716. {$S SoundUnit}
  717. FUNCTION ChanAvailable(theChan: SndChannelPtr): OSErr;
  718.  
  719. {This routine will test the given channel to see if it will really produce
  720.  sound.  The Sound Manager in System 6.0x will return noErr even if the
  721.  channel isn’t going to work.  In the future the Sound Manager will return
  722.  the proper error.  Until then, I use this routine to determine this for me.
  723.  There can only be a single channel at any time, unless I have the wave
  724.  table synthesizer open.  This will allow four channels.  Channels have
  725.  a pointer to the next channel, and if this is not NIL I suspect the
  726.  given channel will not work.  I test the given channel for being a
  727.  wave type, and if so I need to see if the other channels I’ve got are
  728.  also wave type.  If it doesn’t look like the channels is available, I
  729.  return badChannel.  It is important to set the userInfo field of a channel
  730.  before calling this routine!
  731.  
  732.  This routine assumes theChan passed in is the first channel allocated.
  733.  Channels are held in a linked list, and the first one to be tested has
  734.  to be the first one allocated.
  735.  
  736.  BUG NOTE: If an application is not using the Sound Manager and instead
  737.  uses the older Sound Driver, any given channel will fail.  Or if the other
  738.  application does not release is channels, then my channels will not work.
  739.  The most noticeable offender of this is HyperCard.  Friendly applications
  740.  will dispose of their channels at suspend/resume times or ASAP.
  741.  
  742.  Version 1.1: Added the new Sound Manager test.  The new Sound Manager will
  743.  allow multiple sound channels, and returns proper error codes.}
  744.  
  745.     FUNCTION IsMyChan(chan: SndChannelPtr): BOOLEAN;
  746.     BEGIN
  747.         IsMyChan:= ((chan = SndChannelPtr(gChan1))         {is it one of ours?}
  748.                     | (chan = SndChannelPtr(gChan2))
  749.                     | (chan = SndChannelPtr(gChan3))
  750.                     | (chan = SndChannelPtr(gChan4)));
  751.     END;
  752.  
  753.     FUNCTION CompatibleChan(waveChan: SndChannelPtr): BOOLEAN;
  754.     BEGIN
  755.         CompatibleChan:= (waveChan^.userInfo = waveTableSynth)    {wave or..}
  756.                             | (waveChan^.userInfo = kChanFree);            {free chan}
  757.     END;
  758.  
  759. BEGIN
  760.     ChanAvailable:= noErr;
  761.     IF NOT gNewSndMgr THEN BEGIN
  762.         IF theChan^.nextChan <> NIL THEN BEGIN                            {looks bad}
  763.             ChanAvailable:= badChannel;                                    {prepare to fail}
  764.             IF theChan^.userInfo = waveTableSynth THEN BEGIN        {last attempt}
  765.                 IF IsMyChan(theChan^.nextChan)
  766.                  & CompatibleChan(SndChannelPtr(gChan1))
  767.                  & CompatibleChan(SndChannelPtr(gChan2))
  768.                  & CompatibleChan(SndChannelPtr(gChan3))
  769.                  & CompatibleChan(SndChannelPtr(gChan4))
  770.                     THEN
  771.                         ChanAvailable:= noErr;                                {got lucky}
  772.             END;
  773.         END;
  774.     END;
  775. END;
  776.  
  777. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  778. {$S SoundUnit}
  779. FUNCTION SndDataAvailable(sndHandle: Handle): OSErr;
  780.  
  781. {Given a resource handle, this will attempt to load it into memory.  If the
  782.  data is not available, then return an error.}
  783.  
  784. BEGIN
  785.     SndDataAvailable:= noErr;
  786.     IF sndHandle <> NIL THEN BEGIN
  787.         LoadResource(sndHandle);
  788.         IF sndHandle^ = NIL THEN
  789.             SndDataAvailable:= nilHandleErr;            {master pointer is NIL}
  790.     END ELSE
  791.         SndDataAvailable:= nilHandleErr;                {user passed a NIL handle}
  792. END;
  793.  
  794. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  795. {$S SoundUnit}
  796. FUNCTION HoldSnd(sndHandle: Handle): OSErr;
  797.  
  798. {This is used to put the given sound resource into memory and hold it there.
  799.  I also use a MoveHHi to keep the heap from being fragmented.  If this
  800.  fails, then I return an error.  I dereference the handle and check if the
  801.  master pointer is NIL.  This would mean the data could not be loaded.}
  802.  
  803. BEGIN
  804.     IF sndHandle <> NIL THEN BEGIN
  805.         LoadResource(sndHandle);
  806.         IF sndHandle^ = NIL THEN
  807.             HoldSnd:= nilHandleErr                        {master pointer is NIL}
  808.         ELSE BEGIN
  809.             HoldSnd:= noErr;
  810.             MoveHHi(sndHandle);
  811.             HLock(sndHandle);
  812.         END;
  813.     END ELSE
  814.         HoldSnd:= nilHandleErr;                            {user passed a NIL handle}
  815. END;
  816.  
  817. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  818. {$S SoundUnit}
  819. FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
  820.  
  821. {This routine will return the 'snth' resource ID specified by the sound.
  822.  I use this to determine if the given sound will work with _SndPlay.
  823.  This routine does not require the data to be in memory when called.  It
  824.  also doesn’t lock it down while looking for the information.  I will
  825.  mark the resource as being purgeable in the case the resource attributes
  826.  does not have it’s purgeable bit set.
  827.  
  828.  VERSION 1.1:  If no synth information is found in the snd then by default
  829.  it is assumed to be for the squareWaveSynth.}
  830.  
  831. VAR
  832.     soundPtr:    Ptr;
  833.     theErr:        OSErr;
  834.  
  835. BEGIN
  836.     GetSynthInfo.synthID:= kNoSynth;
  837.     GetSynthInfo.initOption:= 0;
  838.     theErr:= SndDataAvailable(sndHandle);
  839.     IF theErr = noErr THEN BEGIN
  840.         soundPtr:= sndHandle^;
  841.         IF Snd1HdrPtr(soundPtr)^.format = firstSoundFormat THEN BEGIN
  842.             IF Snd1HdrPtr(soundPtr)^.numSynths <> kNoSynth THEN BEGIN
  843.                 soundPtr:= Ptr(ORD4(soundPtr) + SizeOf(Snd1Header));
  844.                 GetSynthInfo.synthID:= SynthInfoPtr(soundPtr)^.synthID;
  845.                 GetSynthInfo.initOption:= SynthInfoPtr(soundPtr)^.initOption;
  846.             END ELSE
  847.                 GetSynthInfo.synthID:= squareWaveSynth;
  848.         END ELSE BEGIN {snd is a format 2 for HyperCard}
  849.             GetSynthInfo.synthID:= sampledSynth;
  850.             GetSynthInfo.initOption:= 0;        {no options currently supported}
  851.         END;
  852.     HPurge(sndHandle);
  853.     END;
  854. END;
  855.  
  856. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  857. {$S SoundUnit}
  858. FUNCTION GetSndDataOffset(sndHandle: Handle;
  859.                                     VAR dataType, waveLength: INTEGER): LONGINT;
  860.  
  861. {This routine will cruise through the given snd resource.  It will locate
  862.  the sound data, if any, and return its type and offset into the resource.
  863.  I prefer to return an offset instead of a pointer because I don’t want
  864.  to have the data locked in memory.  If I return an offset, the caller
  865.  can decide when and if it wants the resource locked down to access the
  866.  sound data.  The first step in finding this data is to determine if I’m
  867.  looking at a format 1 or 2 type snd.  A type 2 is easy, but a type 1 will
  868.  require me to find the number of snths specified and then to skip over
  869.  each one including the init option.  Once this is done, I have a pointer
  870.  to the number of commands in the snd.  When I’ve found the first one, I
  871.  examine it to find out if it is a sound data command.  Being it’s a sound
  872.  resource, the command will also have its dataPointerFlag set.  Once I’ve
  873.  found a command I’m looking for I return its type and offset, then get out
  874.  of the REPEAT block.  OTHERWISE I go on to the next command.  All of this
  875.  makes it possible to get the sound data for use as an instrument sound.
  876.  Typically this will be a sampled sound.
  877.  
  878.  dataType is the type of data contained in the snd handle.  This would
  879.  be either sampled sound, a wave table, or possibly no snth is specified.
  880.  The later could be used for any of the snth channels.
  881.  
  882.  waveLength is set to the length of the wave table data, if any is present.
  883.  
  884.  WARNING: Do not send this routine a NIL handle.}
  885.  
  886. VAR
  887.     synths,
  888.     howManyCmds:        INTEGER;
  889.     cruisePtr:            Ptr;
  890.  
  891. BEGIN
  892.     GetSndDataOffset:= 0;
  893.     dataType:= kNoSynth;
  894.     waveLength:= 0;
  895.     cruisePtr:= sndHandle^;
  896.     IF cruisePtr <> NIL THEN BEGIN
  897.         IF Snd1HdrPtr(cruisePtr)^.format = firstSoundFormat THEN BEGIN
  898.             synths:= Snd1HdrPtr(cruisePtr)^.numSynths;
  899.             cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd1Header));
  900.             cruisePtr:= Ptr(ORD4(cruisePtr) + (SizeOf(SynthInfo) * synths));
  901.         END ELSE
  902.             cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd2Header));
  903.         howManyCmds:= IntPtr(cruisePtr)^;    {pointing at number of cmds}
  904.         cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(howManyCmds));
  905.  
  906.                                     {cruisePtr is now at the first sound command}
  907.         REPEAT                    {cruise all commands and find a soundCmd or bufferCmd}
  908.             CASE SndCmdPtr(cruisePtr)^.cmd OF
  909.  
  910.             (soundCmd + dataOffsetFlag), (bufferCmd + dataOffsetFlag):
  911.                 BEGIN
  912.                     dataType:= sampledSynth;
  913.                     GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
  914.                     howManyCmds:= 0;                    {done, get out of loop}
  915.                 END;
  916.  
  917.             (waveTableCmd + dataOffsetFlag):
  918.                 BEGIN
  919.                     dataType:= waveTableSynth;
  920.                     waveLength:= SndCmdPtr(cruisePtr)^.param1;
  921.                     GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
  922.                     howManyCmds:= 0;                    {done, get out of loop}
  923.                 END;
  924.  
  925.             OTHERWISE                                    {catch any other type of cmd}
  926.                 BEGIN
  927.                     cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(sndCommand));
  928.                     howManyCmds:= howManyCmds - 1;
  929.                 END;
  930.  
  931.             END;
  932.         UNTIL howManyCmds < 1;                        {done with all the commands}
  933.     END;
  934. END;
  935.  
  936. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  937. {$S SoundUnit}
  938. FUNCTION SupportedSH(sndPtr: SoundHeaderPtr): BOOLEAN;
  939.  
  940. {VERSION 1.1:  Check the given sound header as being supported by the
  941.  running Sound Manager.  The encode fields of the header are tested.
  942.  The Standard encode always works.  The Compressed sound will only work if
  943.  MACE is present.  A MACE sound can also be a stereo sound, which will only
  944.  work on stereo hardware.  The Expanded sound is for a stereo sound and/or
  945.  16bit samples and is only supported by the new Sound Manager.  So far,
  946.  only 8bit samples are supported.  If the sound is a stereo sound, then
  947.  it requires stereo hardware.}
  948.  
  949. BEGIN
  950.     CASE sndPtr^.encode OF
  951.     
  952.         stdSH:
  953.             SupportedSH:= TRUE;
  954.  
  955.         cmpSH: BEGIN
  956.             IF gHasMACE THEN BEGIN
  957.                 SupportedSH:= (CmpSoundHeaderPtr(sndPtr)^.numChannels = 1)
  958.                                      | ((CmpSoundHeaderPtr(sndPtr)^.numChannels > 1)
  959.                                     & gHasStereo);
  960.             END ELSE
  961.                 SupportedSH:= FALSE;
  962.         END;
  963.         
  964.         extSH: BEGIN
  965.             IF gNewSndMgr THEN BEGIN
  966.                 IF ExtSoundHeaderPtr(sndPtr)^.sampleSize <> 8 THEN
  967.                     SupportedSH:= FALSE
  968.                 ELSE
  969.                     SupportedSH:= (CmpSoundHeaderPtr(sndPtr)^.numChannels = 1)
  970.                                          | ((CmpSoundHeaderPtr(sndPtr)^.numChannels > 1)
  971.                                         & gHasStereo);
  972.             END ELSE
  973.                 SupportedSH:= FALSE;
  974.         END;
  975.  
  976.     END;
  977. END;
  978.  
  979. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  980. {$S SoundUnit}
  981. FUNCTION InstallSampleSnd(myChan: MyChanPtr; sndHandle: Handle): OSErr;
  982.  
  983. {Given a channel and sampled sound resource, this routine will install the
  984.  sound into the channel for use as an instrument.  This allows an
  985.  application to send freqDurationCmds to the channel and play a melody.
  986.  If I sent a bufferCmd instead of the soundCmd, the Sound Manager would play
  987.  the sampled sound.  This is basically what _SndPlay would do with a format 2
  988.  snd.  I insure that I am using only the proper buffer format having the
  989.  standard encode option.  If I were to support compressed sounds, I would
  990.  have to call the MACE synthesizers to expand the buffer before I can use
  991.  it as an instrument.  If I don’t get a sampled sound of standard encoding
  992.  I’ll return a bad format error.  I use _SndDoImmediate to get the sound
  993.  installed because I don’t want this command to be queued.
  994.  
  995.  VERSION 1.1:  Check for a supported sound header.}
  996.  
  997. VAR
  998.     theCmd:             sndCommand;
  999.     dataPtr:            SoundHeaderPtr;
  1000.     dataOffset:        LONGINT;
  1001.     sndDataType,
  1002.     ignore:            INTEGER;
  1003.     theErr:            OSErr;
  1004.  
  1005. BEGIN
  1006.     theErr:= HoldSnd(sndHandle);
  1007.     IF theErr = noErr THEN BEGIN
  1008.         dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1009.         IF sndDataType = sampledSynth THEN BEGIN
  1010.             dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1011.             IF stdSH = dataPtr^.encode THEN BEGIN
  1012.                 WITH theCmd DO BEGIN
  1013.                     cmd:= soundCmd;
  1014.                     param1:= 0;
  1015.                     param2:= ORD4(dataPtr);
  1016.                 END;
  1017.                 myChan^.dataHandle:= sndHandle;
  1018.                 theErr:= SndDoImmediate(SndChannelPtr(myChan), theCmd);
  1019.             END ELSE
  1020.                 theErr:= badFormat;                            {return a bad format error}
  1021.         END ELSE
  1022.             theErr:= badFormat;                                {return a bad format error}
  1023.         IF theErr <> noErr THEN BEGIN
  1024.             HUnlock(sndHandle);                                {and free up the resource}
  1025.             HPurge(sndHandle);
  1026.         END;
  1027.     END;
  1028.     InstallSampleSnd:= theErr;
  1029. END;
  1030.  
  1031. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1032. {$S SoundUnit}
  1033. FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
  1034.                                                         sndInstrument: Handle): OSErr;
  1035.  
  1036. {This routine will create a sampled sound channel using the INIT option
  1037.  given.  Typically this will be 0.  In any case with System 6.0x this
  1038.  option is ignored by the sampled sound synthesizer.  The given sound
  1039.  resource will be installed into the channel for use as an instrument.
  1040.  
  1041.  WARNING: If the application does not want an instrument sound, then the
  1042.  sndInstrument handle MUST be passed in as NIL.
  1043.  
  1044.  BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
  1045.  a Memory Manager error when allocating its internal buffer.  There is a
  1046.  call to NewPtr(1316) and if a NIL is returned, the Sound Manager will
  1047.  write randomly to low memory.  This can occur when calling _SysBeep under
  1048.  low memory conditions.  Also, this pointer is allocated into the
  1049.  application’s heap instead of the system’s.}
  1050.  
  1051. VAR
  1052.     theErr:        OSErr;
  1053.  
  1054. BEGIN
  1055.     FreeAllChans;
  1056.     theErr:= SndNewChannel(SndChannelPtr(gChan1), sampledSynth,
  1057.                                                                         init, @DoCallBack);
  1058.     IF theErr = noErr THEN BEGIN
  1059.         gChan1^.theChan.userInfo:= sampledSynth;
  1060.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1061.         IF (theErr = noErr) & (sndInstrument <> NIL) THEN
  1062.             theErr:= InstallSampleSnd(gChan1, sndInstrument);
  1063.     END;
  1064.     IF theErr <> noErr THEN
  1065.         FreeAllChans
  1066.     ELSE
  1067.         gChanOpen:= TRUE;                                        {got an open channel}
  1068.     sampleChan:= SndChannelPtr(gChan1);
  1069.     GetSampleChan:= theErr;
  1070. END;
  1071.  
  1072. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1073. {$S SoundUnit}
  1074. FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
  1075.                             waveLength: INTEGER): OSErr;
  1076.  
  1077. {Given a channel and pointer to a wave table, this will install the wave
  1078.  for use as an instrument into the channel.  If I find the application
  1079.  giving me a NIL pointer, I’ll return an error.  I use _SndDoImmediate
  1080.  to get the sound installed because I don’t want this to be queued.}
  1081.  
  1082. VAR
  1083.     theCmd:        SndCommand;
  1084.  
  1085. BEGIN
  1086.     IF aWavePtr <> NIL THEN BEGIN
  1087.         WITH theCmd DO BEGIN
  1088.             cmd:= waveTableCmd;
  1089.             param1:= waveLength;
  1090.             param2:= ORD4(aWavePtr);
  1091.         END;
  1092.         InstallWave:= SndDoImmediate(waveChan, theCmd);
  1093.     END ELSE
  1094.         InstallWave:= memPCErr;                                {Pointer Check failed}
  1095. END;
  1096.  
  1097. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1098. {$S SoundUnit}
  1099. FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
  1100.                                   waveChan3, waveChan4: SndChannelPtr): OSErr;
  1101.  
  1102. {This will return four wave table channels with their waves installed.
  1103.  When I create a channel I will set the userInfo field marking it with the
  1104.  'snth' associated to it.  This must be done before calling ChanAvailable.
  1105.  Otherwise that test will fail.  If I cannot obtain all four wave channels
  1106.  I will dispose of the ones I did get before returning the error.  This
  1107.  routine expects to find four wave table pointers, or it will fail.}
  1108.  
  1109. VAR
  1110.     theErr:        OSErr;
  1111.  
  1112.     PROCEDURE NewWaveChan(VAR myChan: MyChanPtr; init: INTEGER);
  1113.     BEGIN
  1114.         theErr:= SndNewChannel(SndChannelPtr(myChan), waveTableSynth,
  1115.                                                                         init, @DoCallBack);
  1116.         IF theErr = noErr THEN BEGIN
  1117.             myChan^.theChan.userInfo:= waveTableSynth;
  1118.             theErr:= ChanAvailable(SndChannelPtr(myChan));
  1119.         END;
  1120.     END;
  1121.  
  1122. BEGIN
  1123.     FreeAllChans;
  1124.     NewWaveChan(gChan1, initChan0);
  1125.     IF theErr = noErr THEN BEGIN
  1126.         NewWaveChan(gChan2, initChan1);
  1127.         IF theErr = noErr THEN BEGIN
  1128.             NewWaveChan(gChan3, initChan2);
  1129.             IF theErr = noErr THEN BEGIN
  1130.                 NewWaveChan(gChan4, initChan3);
  1131.             END;
  1132.         END;
  1133.     END;
  1134.     IF theErr <> noErr THEN
  1135.         FreeAllChans                                            {we didn’t make it}
  1136.     ELSE BEGIN
  1137.         waveChan1:= SndChannelPtr(gChan1);
  1138.         waveChan2:= SndChannelPtr(gChan2);
  1139.         waveChan3:= SndChannelPtr(gChan3);
  1140.         waveChan4:= SndChannelPtr(gChan4);
  1141.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1142.     END;
  1143.     GetWaveChans:= theErr;
  1144. END;
  1145.  
  1146. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1147. {$S SoundUnit}
  1148. FUNCTION GetSquareChan(VAR squareChan: SndChannelPtr; timbre: INTEGER): OSErr;
  1149.  
  1150. {This will create a channel for the square wave synthesizer.  When I create a
  1151.  channel I will set the userInfo field marking it with the 'snth'
  1152.  associated to it.  This must be done before calling ChanAvailable.
  1153.  Otherwise that test will fail.  There are no INIT options used by this
  1154.  synthesizer, but I will set the timbre to adjust the tone quality.}
  1155.  
  1156. VAR
  1157.     theErr:        OSErr;
  1158.  
  1159. BEGIN
  1160.     FreeAllChans;
  1161.     theErr:= SndNewChannel(SndChannelPtr(gChan1), squareWaveSynth,
  1162.                                                                 kInitNone, @DoCallBack);
  1163.     IF theErr = noErr THEN BEGIN
  1164.         gChan1^.theChan.userInfo:= squareWaveSynth;
  1165.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1166.         IF theErr = noErr THEN
  1167.             theErr:= SetSquareTimbre(SndChannelPtr(gChan1), timbre, NOT kWait);
  1168.     END;
  1169.     IF theErr <> noErr THEN
  1170.         FreeAllChans
  1171.     ELSE
  1172.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1173.     squareChan:= SndChannelPtr(gChan1);
  1174.     GetSquareChan:= theErr;
  1175. END;
  1176.  
  1177. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1178. {$S SoundUnit}
  1179. FUNCTION GetNoSynthChan(VAR chan: SndChannelPtr): OSErr;
  1180.  
  1181. {This is the routine to create a channel that isn’t associated with any
  1182.  synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  1183.  you need to get such a channel.  When I create a channel I will set the
  1184.  userInfo field marking it with the 'snth' associated to it.  This must be
  1185.  done before calling ChanAvailable.
  1186.  
  1187.  BUG NOTE: Do not use a channel already associated to a snth with
  1188.  _SndPlay.  This causes the Sound Manager to install a second copy of the
  1189.  same snth.}
  1190.  
  1191. VAR
  1192.     theErr:        OSErr;
  1193.  
  1194. BEGIN
  1195.     FreeAllChans;
  1196.     theErr:= SndNewChannel(SndChannelPtr(gChan1), kNoSynth,
  1197.                                                                 kInitNone, @DoCallBack);
  1198.     IF theErr = noErr THEN BEGIN
  1199.         gChan1^.theChan.userInfo:= kNoSynth;
  1200.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1201.     END;
  1202.     IF theErr <> noErr THEN
  1203.         FreeAllChans
  1204.     ELSE
  1205.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1206.     chan:= SndChannelPtr(gChan1);
  1207.     GetNoSynthChan:= theErr;
  1208. END;
  1209.  
  1210. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1211. {$S SoundUnit}
  1212. FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
  1213.  
  1214. {This routine will use the given channel and snd resource with _SndPlay.
  1215.  This is used to play a sound, which is a series of sound commands commonly
  1216.  referred to as a sequence.  First thing I do is make sure the song fits in
  1217.  memory.  _SndPlay will lock this resource in memory and then pump the snd
  1218.  for all of its worth.  I am calling it asynchronously, and if I was using
  1219.  a snd that contained sound data I wouldn’t mark the snd as being
  1220.  purgeable.  But in this case, _SndPlay will be done with the snd as soon
  1221.  as it returns because it copied all of the commands into the channel.
  1222.  (There’s no data associated with a sequence, just commands.)  _SndPlay
  1223.  will not return until it has done so.  After _SndPlay I need to work
  1224.  around a bug in the freqDurationCmd.  The last thing to do is to send a
  1225.  callBackCmd to signal me that the channel has completed.  If any Sound
  1226.  Manager errors are encountered, I return them to the application.  If the
  1227.  application passed me a NIL snd handle, I’ll return an error.
  1228.  
  1229.  WARNING: Make sure you are using a snd that only has frequency type commands
  1230.  in it and not something such as a bufferCmd.
  1231.  
  1232.  BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
  1233.  The sound will continue to be heard, looping forever, until a quietCmd is sent
  1234.  or the channel is disposed of.  To prevent unwanted looping, I send a
  1235.  quietCmd after all frequency commands.  Also read a related bug note when
  1236.  disposing of channels in the routine FreeChan.}
  1237.  
  1238. VAR
  1239.     theErr:            OSErr;
  1240.  
  1241. BEGIN
  1242.     theErr:= SndDataAvailable(sndSong);                {get the data loaded}
  1243.     IF theErr = noErr THEN BEGIN
  1244.         theErr:= SndPlay(chan, sndSong, kSMAsynch);    {pump the sound}
  1245.         HUnlock(sndSong);
  1246.         HPurge(sndSong);
  1247.         IF theErr = noErr THEN BEGIN
  1248.             theErr:= SendQuiet(chan, kWait);            {work around bug}
  1249.             IF theErr = noErr THEN
  1250.                 theErr:= SoundComplete(chan);
  1251.         END;
  1252.     END ELSE
  1253.         PlaySong:= nilHandleErr;                        {snd data was not available}
  1254.     IF theErr <> noErr THEN
  1255.         FreeAllChans;
  1256.     PlaySong:= theErr;
  1257. END;
  1258.  
  1259. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1260. {$S SoundUnit}
  1261. FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
  1262.  
  1263. {This is used to send a syncCmd to a channel and causes the other channels
  1264.  that are being held by a synchCmd to be released.  Of course, this assumes
  1265.  the application has already called SynchChans.  _SndDoImmediate is used
  1266.  to get the command directly to the synthesizer bypassing the queue.
  1267.  
  1268.  BUG NOTE: I’ve found that immediately clearing the channels and starting
  1269.  new ones may cause the channels to startup playing out of synch?  This
  1270.  happens while disposing the wave channels and starting them immediately.}
  1271.  
  1272. VAR
  1273.     theCmd:             sndCommand;
  1274.  
  1275. BEGIN
  1276.     WITH theCmd DO BEGIN
  1277.         cmd:= syncCmd;
  1278.         param1:= 1;
  1279.         param2:= kSyncID;
  1280.     END;
  1281.     ReleaseSynch:= SndDoImmediate(chan, theCmd);
  1282. END;
  1283.  
  1284. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1285. {$S SoundUnit}
  1286. FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
  1287.  
  1288. {This is used to synchronize four wave table channels.  By first sending
  1289.  the synchCmd, I can send a sequence of other commands to the channel and
  1290.  not have the channel attempt to start processing any of them.  That is until
  1291.  another synchCmd is sent causing all of the previous synchCmd’s counter
  1292.  to be decremented.  After getting all the channels in synch and sending
  1293.  the sequence of further commands, then use the ReleaseSynch routine to
  1294.  start all of the channels processing their respective queues.
  1295.  _SndDoImmediate is used to get the command directly to the synthesizer
  1296.  bypassing the queue.}
  1297.  
  1298. VAR
  1299.     theCmd:             SndCommand;
  1300.     theErr:            OSErr;
  1301.  
  1302.     PROCEDURE Synch1Chan(chan: SndChannelPtr; count: INTEGER);
  1303.     BEGIN
  1304.         WITH theCmd DO BEGIN
  1305.             cmd:= syncCmd;
  1306.             param1:= count;
  1307.             param2:= kSyncID;
  1308.         END;
  1309.         theErr:= SndDoImmediate(chan, theCmd);
  1310.     END;
  1311.  
  1312. BEGIN
  1313.     Synch1Chan(chan4, 5);
  1314.     IF theErr = noErr THEN BEGIN
  1315.         Synch1Chan(chan3, 4);
  1316.         IF theErr = noErr THEN BEGIN
  1317.             Synch1Chan(chan2, 3);
  1318.             IF theErr = noErr THEN BEGIN
  1319.                 Synch1Chan(chan1, 2);
  1320.             END;
  1321.         END;
  1322.     END;
  1323.     SynchChans:= theErr;
  1324. END;
  1325.  
  1326. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1327. {$S SoundUnit}
  1328. FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
  1329.                             song1, song2, song3, song4: Handle): OSErr;
  1330.  
  1331. {In order to synchronize channels, the synchCmd is needed.  Once all of the
  1332.  song has been sent into each channel, a final synchCmd is issued to
  1333.  release them.  Don’t send more commands into a channel that it can hold at
  1334.  one time while the channel is in synch mode.}
  1335.  
  1336. VAR
  1337.     theErr:            OSErr;
  1338.  
  1339. BEGIN
  1340.     theErr:= SynchChans(waveChan1, waveChan2, waveChan3, waveChan4);
  1341.     IF theErr = noErr THEN BEGIN
  1342.     theErr:= PlaySong(waveChan1, song1);
  1343.     IF theErr = noErr THEN BEGIN
  1344.         theErr:= PlaySong(waveChan2, song2);
  1345.         IF theErr = noErr THEN BEGIN
  1346.             theErr:= PlaySong(waveChan3, song3);
  1347.             IF theErr = noErr THEN BEGIN
  1348.                 theErr:= PlaySong(waveChan4, song4);
  1349.                 IF theErr = noErr THEN
  1350.                     theErr:= ReleaseSynch(waveChan1);
  1351.                 END;
  1352.             END;
  1353.         END;
  1354.     END;
  1355.     Play4Waves:= theErr;
  1356. END;
  1357.  
  1358. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1359. {$S SoundUnit}
  1360. FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
  1361.  
  1362. {WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE.  I’ve provided
  1363.  this routine because people have asked me how HyperCard performs its PLAY
  1364.  command and why their HyperCard sounds do not sound right using the
  1365.  _SndPlay routine.  The correct answer is that _SndPlay plays the sound
  1366.  correctly.  HyperCard is attempting to change the frequency by adjusting
  1367.  the sample rate.  This is NOT the correct approach.  Define the sound
  1368.  buffer as it should be played.  _SndPlay plays the sound as it is defined.
  1369.  If the result from _SndPlay is not what you want, then it is the sample
  1370.  that is incorrect and should be edited.  The sample rate is the rate at
  1371.  which the sound was recorded.  If you didn’t record it, then how do you
  1372.  know what’s the correct rate?  Set the baseFrequency to the frequency that
  1373.  was recorded.  If you recorded middle C at 22k, then the rate is 22k and the
  1374.  baseFrequency is middle C.  HyperCard is incorrect in using the sample rate
  1375.  as the frequency of the sound.  Furthermore, using this technique of
  1376.  calculating a new sample rate can introduce errors.  The resulting sample
  1377.  rate will not be the proper pitch.  Also, the sample rate for high pitches
  1378.  will be very inaccurate and impossible for the Mac to reproduce.  Such a
  1379.  problem can happen if the given sample rate was 22k and is to be played
  1380.  back at three octaves higher.  Even 44k samples transposed up a half
  1381.  octave will fail.  Using the soundCmd and freqDurationCmd will not have
  1382.  this problem.
  1383.  
  1384.  Given a sound resource, this routine will play it in the manner that
  1385.  HyperCard does.  HyperCard assumes that a sound is to be played at middle
  1386.  C when the user does not specify a frequency value in the PLAY command.  I
  1387.  don’t know why.  (What’s middle C when I want to hear speech or the sound
  1388.  of crickets?)  At any rate (pun intended), I get a sampled sound channel.
  1389.  I find the sound data offset in the resource, which has to be locked down
  1390.  at this time.  Once I have the sound data, I get its original sample rate.
  1391.  I have to calculate what a new sample rate would be based on its baseFrequency.
  1392.  The baseFrequency is the frequency at which the sound was recorded.  I’m
  1393.  not sure what this means to crickets, but if this is set to middle C then
  1394.  HyperCard doesn’t attempt to modify the sample rate.  (If you’re wondering
  1395.  how the math works in this routine, buy a book on music theory.  I’m here to
  1396.  provide Mac support.)  Once I’ve adjusted the sample rate, I use the
  1397.  bufferCmd to play it.  Then I restore the sound resource to its original
  1398.  state.  If I didn’t do this it would be possible that the resource was
  1399.  still in memory the next time I use it having the adjusted sample rate.
  1400.  This would cause me to incorrectly adjust it again.  Unlike HyperCard, I
  1401.  can do this for both a format 1 and 2.
  1402.  
  1403.  BUG NOTE: Do not call SANE of the FPU while the Sound Manager is running.
  1404.  Refer to Tech Note #235.  This problem was fixed in the new Sound Manager.
  1405.  
  1406.  VERSION 1.1:  Replaced the previous test of the sound header's encode
  1407.  value.  The previous version was incorrect.  The bufferCmd will automatically
  1408.  de-code a MACE compressed sound if MACE is available.  Otherwise, the sound
  1409.  will not be able to be used.  So, a new routine is being used to check for
  1410.  supported sound headers.  The conflict with the Sound Manager and SANE was
  1411.  resolved in the new Sound Manager.  It no longers uses extended numbers, and
  1412.  instead uses fixed math.}
  1413.  
  1414. VAR
  1415.     theCmd:                 SndCommand;
  1416.     newRate:                Extended;
  1417.     oldRate:                Fixed;
  1418.     dataPtr:                SoundHeaderPtr;
  1419.     dataOffset:            LONGINT;
  1420.     sndDataType,
  1421.     power, ignore:        INTEGER;
  1422.     theErr:                OSErr;
  1423.  
  1424. BEGIN
  1425.     theErr:= HoldSnd(sndHandle);
  1426.     IF theErr = noErr THEN BEGIN
  1427.         theErr:= GetSampleChan(SndChannelPtr(gChan1), kInitNone, NIL);
  1428.         gChan1^.dataHandle:= sndHandle;        {so FreeAllChans can dispose of data}
  1429.         IF theErr = noErr THEN BEGIN
  1430.  
  1431.             dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1432.             IF sndDataType = sampledSynth THEN BEGIN
  1433.                 dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1434.                 IF SupportedSH(dataPtr) THEN BEGIN
  1435.                     oldRate:= dataPtr^.sampleRate;            {save original sample rate}
  1436.                     IF NOT (dataPtr^.baseFrequency = kMiddleC) THEN BEGIN
  1437.                         IF dataPtr^.sampleRate < 0 THEN         {large positive number}
  1438.                             newRate:= (Fix2X(BAnd(dataPtr^.sampleRate, maxLongInt))
  1439.                                             + maxLongInt + 1)        {twos comp. is off by one}
  1440.                         ELSE
  1441.                             newRate:= Fix2X(dataPtr^.sampleRate);
  1442.                         newRate:= Fix2X(dataPtr^.sampleRate);
  1443.                         power:= kMiddleC - dataPtr^.baseFrequency;
  1444.                         dataPtr^.sampleRate:= X2Fix((XpwrI(twelfthRootTwo, power))
  1445.                                                              * newRate);
  1446.                     END;
  1447.                     WITH theCmd DO BEGIN
  1448.                         cmd:= bufferCmd;
  1449.                         param1:= 0;
  1450.                         param2:= ORD4(dataPtr);
  1451.                     END;
  1452.                     theErr:= SndDoImmediate(SndChannelPtr(gChan1), theCmd);
  1453.                     IF theErr = noErr THEN
  1454.                         theErr:= SoundComplete(SndChannelPtr(gChan1));
  1455.                     dataPtr^.sampleRate:= oldRate;        {restore original sample rate}
  1456.                 END ELSE
  1457.                     theErr:= badFormat;                        {not SupportedSH}
  1458.             END ELSE
  1459.                 theErr:= badFormat;                            {sndDataType not sampledSynth}
  1460.         END;
  1461.     END;
  1462.     IF theErr <> noErr THEN
  1463.         FreeAllChans;
  1464.     HyperSndPlay:= theErr;
  1465. END;
  1466.  
  1467. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1468. {$S SoundUnit}
  1469. FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
  1470.  
  1471. {Given a sound resource, this routine will call _SndPlay.  The snd must
  1472.  be either a format 2 or format 1 that contains snth information.
  1473.  Using _SndPlay asynchronously requires us to lock the snd prior to
  1474.  calling the trap.  The reason being is _SndPlay remembers the state of the
  1475.  lock bit using _HGetState and _HSetState.  If the snd is unlocked when
  1476.  it’s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
  1477.  This would be bad when using the sound asynchronously.  If the sound being
  1478.  passed in happens to be a compressed sound created with MACE, it will “do
  1479.  the right thing.”  If MACE isn’t around the Sound Manager will pretend to
  1480.  play a sound but nothing will be heard.
  1481.  
  1482.  BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  1483.  a Memory Manager error when allocating its internal buffer.  There is a
  1484.  call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
  1485.  randomly to memory.  Also, the pointer is allocated into the application’s
  1486.  heap instead of the system’s.
  1487.  
  1488.  BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  1489.  a bogus callBackCmd into the channel.  This will cause the user’s call
  1490.  back procedure to be called as soon as the sound has completed.  Refer
  1491.  to the DoCallBack routine for details.
  1492.  
  1493.  VERSION 1.1:  Add the check for the sound header being supported and 
  1494.  replaced the check of the snth information.  No synth information in the
  1495.  snd is valid and would mean to play the snd using the squareWaveSynth.}
  1496.  
  1497. VAR
  1498.     dataPtr:                SoundHeaderPtr;
  1499.     dataOffset:            LONGINT;
  1500.     sndDataType,
  1501.     ignore:                INTEGER;
  1502.     theErr:                OSErr;
  1503.  
  1504. BEGIN
  1505.     theErr:= HoldSnd(sndHandle);                {hold on to the sound}
  1506.     IF theErr = noErr THEN BEGIN
  1507.         theErr:= GetNoSynthChan(SndChannelPtr(gChan1));
  1508.         gChan1^.dataHandle:= sndHandle;        {so FreeAllChans can dispose of data}
  1509.         IF theErr = noErr THEN BEGIN
  1510.             gChan1^.theChan.userInfo:= GetSynthInfo(sndHandle).synthID;
  1511.             dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1512.             IF sndDataType = sampledSynth THEN BEGIN
  1513.                 dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1514.                 IF NOT SupportedSH(dataPtr) THEN
  1515.                     theErr:= badFormat;
  1516.             END;
  1517.             IF theErr = noErr THEN BEGIN
  1518.                 theErr:= SndPlay(SndChannelPtr(gChan1), sndHandle, kSMAsynch);
  1519.                 IF theErr = noErr THEN
  1520.                     theErr:= SoundComplete(SndChannelPtr(gChan1));
  1521.             END;
  1522.         END;
  1523.     END;
  1524.     IF theErr <> noErr THEN
  1525.         FreeAllChans;
  1526.     AsynchSndPlay:= theErr;
  1527. END;
  1528.  
  1529.  
  1530.  
  1531. END. {UNIT}